/**
 * earth - a project to visualize global weather data.
 *
 * Copyright (c) 2020 Cameron Beccario
 *
 * For a free version of this project, see https://github.com/cambecc/earth
 */

import * as d3 from "d3";
import {
  ø
} from "./util/objects.js";
import {
  createAgent
} from "./framework/agent.js";
import {
  $
} from "./util/seq.js";
import {
  computeViewbox,
  defineViewbox
} from "./ux/viewbox.js";
import {
  buildInputController
} from "./ux/inputController.js";
import {
  defineMeshBuilder
} from "./engine/mesh.js";
import {
  defineGlobeBuilder,
  globeTypes
} from "./engine/buildGlobe.js";
import {
  defineRendererBuilder
} from "./engine/renderer.js";
import {
  defineLayerManager
} from "./engine/layers.js";
import {
  defineFieldBuilder
} from "./engine/field.js";
import {
  defineAnimator
} from "./engine/animate.js";
import {
  defineOverlayDrawer
} from "./engine/overlay.js";
import {
  report
} from "./report.js";
import {
  initialSharedState,
  sharedStore
} from "./framework/sharedStore.js";
import {
  sessionStore,
  restoreSessionStore
} from "./framework/sessionStore.js";
import {
  modalStore,
  restoreModalStore
} from "./framework/modalStore.js";
import {
  defineProgressbar
} from "./ux/progressbar.js";
import {
  defineModelButton,
  defineDisclosureButton,
  defineToggleButton
} from "./ux/buttons.js";
import {
  defineRadiogroup
} from "./ux/radiogroup.js";
import {
  defineTablist
} from "./ux/tablist.js";
import {
  defineToolbar
} from "./ux/toolbar.js";
import {
  defineTimeNav
} from "./ux/timeNav.js";
import {
  defineLayerDetails
} from "./ux/layerDetails.js";
import {
  defineTooltips
} from "./ux/tooltips.js";
import {
  defineScrollableUpdater
} from "./ux/scrollable.js";
import {
  defineColorbar
} from "./ux/colorbar.js";
import {
  defineLayerSpotlight,
  defineSpotlight,
  defineSpotlightMarker
} from "./ux/spotlight.js";
import {
  defineModal
} from "./ux/modal.js";
import {
  defineDateChooser
} from "./ux/dateChooser.js";
import {
  mostPreferredLang
} from "./ux/lang.js";
import {
  defineFastoverlay
} from "./engine/fastoverlayBuilder.js";
import {
  defineShowCurrentPosition
} from "./ux/currentPos.js";
import {
  defineKeyboardShortcuts
} from "./ux/shortcuts.js";
import {
  fromHashFragment,
  toHashFragment
} from "./framework/hashSync.js";
import {
  debounce
} from "underscore";
import {
  overlayTypes
} from "./product/products.js";
import {
  isKioskContext,
  isNonKioskIFrameContext,
  siteInstance
} from "./util/context.js";
import {
  gaEvent
} from "./util/ga.js";
import {
  defineNoticeCard
} from "./ux/noticeCard.js";
import {
  defineCallToAction
} from "./ux/cta.js";
import {
  defineZLevelManager
} from "./ux/zLevel.js";
import {
  switchMode
} from "./ux/mode.js";
// import {defineTaraExtension} from "./extension/tara.js";
// import {getQueryString} from "./util/query.js";

// sharedStore.on($`change`, delta => console.log("shared", delta));
// sessionStore.on($`change`, delta => console.log("session", delta));
// modalStore.on($`change`, delta => console.log("modal", delta));

const handles = {
  animation_enabled: sharedStore.handle("animation_enabled"),
  projection_type: sharedStore.handle("projection_type"),
  orientation: sharedStore.handle("orientation"),
  overlay_type: sharedStore.handle("overlay_type"),
  spotlight_coords: sharedStore.handle("spotlight_coords"),
  z_level: sharedStore.handle("z_level"),
  animation_type: sharedStore.handle("animation_type"),
  mode: sharedStore.handle("mode"),
  time_cursor: sharedStore.handle("time_cursor"),

  display_timezone: sessionStore.handle("display_timezone"),
  progress: sessionStore.handle("progress"),
  notice: sessionStore.handle("notice"),
  hd_enabled: sessionStore.handle("hd_enabled"),
  interpolation_type: sessionStore.handle("interpolation_type"),
  selected_units: sessionStore.handle("selected_units"),
  colorbar_cursor: sessionStore.handle("colorbar_cursor"),
  show_menu: sessionStore.handle("show_menu"),
  font_size: sessionStore.handle("font_size"),
};

function setupLang() {

  // move current lang to front of the list
  const current = d3.select("html").attr("lang");
  d3.select("#lang-menu-modal")
    .select(`a[lang='${current}']`)
    .classed("selected modal-focus", true)
    .select(function() {
      return this.parentElement;
    })
    .lower();

  // show link to preferred lang
  const preferred = mostPreferredLang(window.navigator);
  if (preferred !== current) {
    d3.select(".lang-shortcuts").select(`a[lang='${preferred}']`).property("hidden", false);
  }

}

function setupKiosk() {
  if (isKioskContext()) {
    d3.selectAll(".kiosk").property("hidden", true);
  }
}

function defineScreenFiller(viewboxAgent) {

  function updateDOM() {
    const {
      width,
      height
    } = viewboxAgent.value();
    const ratio = window.devicePixelRatio ?? 1;
    d3.selectAll(".fill-screen")
      .attr("width", width)
      .attr("height", height);
    d3.selectAll(".max-screen")
      .style("width", `${width}px`)
      .style("height", `${height}px`)
      .attr("width", width * ratio)
      .attr("height", height * ratio)
      .nodes()
      .forEach(node => node.getContext("2d").scale(ratio, ratio));
  }

  updateDOM();
  viewboxAgent.on($`update`, updateDOM);
}

function defineEarthButton() {
  function escapeIFrame() {
    window.open(window.location, "_blank");
  }

  const button = d3.selectAll("[data-name=earth]");
  if (isNonKioskIFrameContext()) {
    button.on($`click`, escapeIFrame);
  } else {
    defineDisclosureButton(button, handles.show_menu);
  }

  if (!isKioskContext()) {
    d3.selectAll("[data-name=earth]").on($`click`, () => d3.selectAll(".ham").property("hidden", true));
  }
}

function updateDynamicLinks() {
  function adjust() {
    const e = d3.select(this);
    const link = e.attr("data-link");
    return link ? `${link}${isKioskContext() ? "?kiosk" : ""}${window.location.hash}` : e.attr("href");
  }

  // change all dynamic links to use the current hash fragment so stuff stays the same when they are followed
  d3.selectAll("a[lang]").attr("href", adjust);
  d3.selectAll("a.dyna-link").attr("href", adjust);
}

function startHashSync() {

  function readHashFragment() {
    const currentHash = window.location.hash;
    const attr = fromHashFragment(currentHash, globeTypes, overlayTypes, initialSharedState);
    sharedStore.save(attr, {
      source: "hash_read"
    });
    updateDynamicLinks();
  }

  function writeHashFragment(delta, old, meta) {
    const nextHash = toHashFragment(sharedStore.getAll());
    // write the hash fragment only when there's a difference and it was caused by something other than a hash read
    if (window.location.hash !== nextHash && meta?.source !== "hash_read") {
      window.history.pushState(null, null, nextHash);
      updateDynamicLinks();
    }
  }
  const writeHashFragment_debounced = debounce(writeHashFragment);

  sharedStore.on($`change`, writeHashFragment_debounced);
  d3.select(window).on($`hashchange`, readHashFragment);
  readHashFragment();
}

export function getQueryString() {
  const originUrl = window.location.href
  const arr = originUrl.split("?");
  var valiable = arr[0];
  const queryString = arr[1] || '';
  const currentHash = decodeURIComponent(queryString);
  return currentHash
}

export function getQueryAttr() {
  const currentHash = getQueryString();
  const attr = fromHashFragment(`#${currentHash}`, globeTypes, overlayTypes, initialSharedState);
  return attr
}

function startQuerySync() {

  function readHashFragment() {
    const attr = getQueryAttr();
    sharedStore.save(attr, {
      source: "hash_read"
    });
    updateDynamicLinks();
  }

  function writeHashFragment(delta, old, meta) {
  const originUrl = window.location.href
  const arr = originUrl.split("?");
  var valiable = arr[0];
  const queryString = arr[1] || '';
  const currentHash = decodeURIComponent(queryString);
    // const currentHash = getQueryString();
    var nextHash = toHashFragment(sharedStore.getAll());
    nextHash = nextHash.substring(1);
    // write the hash fragment only when there's a difference and it was caused by something other than a hash read
    if (currentHash !== nextHash && meta?.source !== "hash_read") {
      window.history.pushState(null, null, valiable + "?" + nextHash);
      updateDynamicLinks();
    }
  }
  const writeHashFragment_debounced = debounce(writeHashFragment);

  sharedStore.on($`change`, writeHashFragment_debounced);
  d3.select(window).on($`hashchange`, readHashFragment);
  readHashFragment();
}

function startGoogleAnalyticsNotifier() {
  // UNDONE: disable until it's clear how to use these metrics
  // handles.show_menu.on($`change`, value => value && gaEvent("menu", "open"));
  // handles.projection_type.on($`change`, value => gaEvent("projection_type", value));
  // handles.overlay_type.on($`change`, value => gaEvent("overlay_type", value));
  // handles.animation_type.on($`change`, value => gaEvent("animation_type", value));
  // handles.z_level.on($`change`, value => gaEvent("z_level", value));

  // Original logic:
  // let first = true;
  // sharedConfig.on(seq`change.?`, function(changed) {
  //     if (first) return (first = false);
  //     const attr = sharedConfig.getAll();
  //     if (_.has(changed, "projection_type")) {
  //         gaEvent("projection", attr.projection_type);
  //     }
  //     if (_.has(changed, "overlay_type")) {
  //         gaEvent("overlay", attr.overlay_type);
  //     }
  //     if (_.has(changed, "mode") || _.has(changed, "animation_type") || _.has(changed, "z_level")) {
  //         gaEvent("layer", [attr.mode, attr.animation_type, attr.z_level].join("-"));
  //     }
  // });
}

function notifyTest() {
  if (siteInstance() === "test") {
    handles.notice.set("temporary beta preview");
  }
}

function notifyApp() {
  const [lon = NaN, lat = NaN] = sharedStore.get("orientation")?.rotate ?? [];
  const overlay = sharedStore.get("overlay_type");
  if (lon.toFixed(2) === "232.72" && lat.toFixed(2) === "-37.64" && (overlay === "pm2.5" || overlay === "pm10")) {
    gaEvent("app", "mise");
  }
}

export function earth( /*app*/ ) {

  // const {bridge} = app;

  function newAgent(name, options) {
    return createAgent(ø(options, {
      name
    })).on($`reject fail`, report.error);
  }

  // Construct the page's main internal components:

  const viewboxAgent = newAgent("viewbox", {
    initial: computeViewbox()
  }); // the dimensions of the display
  const meshAgent = newAgent("mesh"); // map data for the earth
  const globeAgent = newAgent("globe"); // the model of the globe
  const layerManagerAgent = newAgent("layer"); // the layers of weather data
  const primaryLayerAgent = newAgent("primaryLayer");
  const overlayLayerAgent = newAgent("overlayLayer");
  const activeLayerAgent = newAgent("activeLayer");
  const rendererAgent = newAgent( // the globe SVG renderer
    "renderer", {
      events: ["start", "render", "redraw"]
    },
  );
  const fieldAgent = newAgent("field"); // the interpolated wind vector field
  const fastoverlayAgent = newAgent("fastoverlay"); // the interpolated wind vector field
  const animatorAgent = newAgent("animator"); // the wind animator
  const overlayAgent = newAgent("overlay"); // color overlay over the animation
  const overlayGridAgent = newAgent("overlayGrid"); // grid points drawn on the overlay

  const inputController = buildInputController(handles.orientation, viewboxAgent); // interprets drag/zoom operations

  function init(done) {
    console.time("init");
    report.status("Initializing...");

    setupLang();
    setupKiosk();

    defineViewbox(viewboxAgent);
    defineScreenFiller(viewboxAgent);

    defineScrollableUpdater(d3.select("#menu"));
    const tooltipController = defineTooltips(d3.selectAll("[role=tooltip]"));

    defineEarthButton();
    defineCallToAction(d3.selectAll(".cta-bar"), sessionStore.handle("show_cta"));
    defineProgressbar(d3.selectAll("[role=progressbar]"), handles.progress);
    defineLayerDetails(d3.select("#menu"), sessionStore, primaryLayerAgent, activeLayerAgent);
    defineColorbar(
      d3.select(".colorbar"),
      handles.colorbar_cursor,
      handles.selected_units,
      activeLayerAgent,
      tooltipController,
    );
    defineRadiogroup(d3.selectAll("[role=radiogroup][data-name=projection_type]"), handles.projection_type);
    defineRadiogroup(
      d3.selectAll("[role=radiogroup][data-name=overlay_type]"),
      handles.overlay_type,
      (name, meta) => {
        const changes = {
          overlay_type: name
        };
        if (name === "wind_power_density" && handles.z_level.get() === "surface") {
          changes.z_level = "1000hPa";
        }
        sharedStore.save(changes, meta);
      },
    );
    defineRadiogroup(d3.selectAll("[role=radiogroup][data-name=z_level]"), handles.z_level);
    defineZLevelManager(primaryLayerAgent, overlayLayerAgent);
    defineRadiogroup(d3.selectAll("[role=radiogroup][data-name=animation_type]"), handles.animation_type);
    defineTablist(d3.selectAll("[role=tablist][data-name=mode]"), handles.mode, switchMode);

    defineRadiogroup(
      d3.selectAll("[role=radiogroup][data-name=projection_type]"),
      handles.projection_type,
      (name, meta) => sharedStore.save({
        projection_type: name,
        orientation: undefined
      }, meta),
    );

    defineToolbar(d3.selectAll("[role=toolbar]"));
    defineModelButton(d3.selectAll("[data-name=nav-now]"), sharedStore, {
      time_cursor: "current"
    });
    defineModelButton(d3.selectAll("[data-name=show-date-chooser]"), modalStore, {
      show_date_chooser: true
    });
    defineTimeNav(d3.selectAll("#nav-arrows"), handles.time_cursor, activeLayerAgent);
    defineTimeNav(d3.selectAll(".title-bar"), handles.time_cursor, activeLayerAgent);
    defineToggleButton(d3.selectAll("[data-name=toggle-grid]"), sharedStore.handle("show_grid_points"));
    defineToggleButton(d3.selectAll("[data-name=toggle-animation]"), sharedStore.handle("animation_enabled"));
    defineToggleButton(d3.selectAll("[data-name=toggle-hd]"), sessionStore.handle("hd_enabled"));
    defineShowCurrentPosition(d3.selectAll("[data-name=show-current-position]"), sharedStore);
    defineModelButton(d3.selectAll("[data-name=show-settings]"), modalStore, {
      show_settings: !isKioskContext()
    });

    defineNoticeCard(d3.selectAll("[data-name=notice-card]"), handles.notice);

    defineSpotlight(d3.select("#spotlight-panel"), handles.spotlight_coords);
    defineLayerSpotlight(
      d3.select("[data-name=spotlight-a]"),
      handles.spotlight_coords,
      handles.selected_units,
      handles.interpolation_type,
      primaryLayerAgent,
    );
    defineLayerSpotlight(
      d3.select("[data-name=spotlight-b]"),
      handles.spotlight_coords,
      handles.selected_units,
      handles.interpolation_type,
      overlayLayerAgent,
    );
    defineSpotlightMarker(
      d3.select("#annotation"),
      globeAgent,
      fieldAgent,
      handles.spotlight_coords,
      inputController,
    );

    defineModal(d3.select("#lang-menu-modal"), modalStore, "show_settings");
    defineDateChooser(
      d3.select("#choose-date-modal"),
      modalStore,
      "show_date_chooser",
      handles.time_cursor,
      activeLayerAgent,
      handles.display_timezone,
    );

    defineGlobeBuilder(globeAgent, handles.projection_type, handles.orientation, viewboxAgent);
    defineMeshBuilder(meshAgent, sessionStore.handle("topology_url"));
    defineRendererBuilder(rendererAgent, viewboxAgent, inputController, globeAgent, meshAgent);
    defineLayerManager(
      layerManagerAgent,
      primaryLayerAgent,
      overlayLayerAgent,
      activeLayerAgent,
      sharedStore,
      sessionStore,
    );
    defineFastoverlay(
      fastoverlayAgent,
      globeAgent,
      activeLayerAgent,
      rendererAgent,
      handles.overlay_type,
      handles.hd_enabled,
      handles.animation_enabled,
      handles.interpolation_type,
      viewboxAgent,
    );
    defineFieldBuilder(
      fieldAgent,
      viewboxAgent,
      globeAgent,
      rendererAgent,
      primaryLayerAgent,
      activeLayerAgent,
      handles.progress,
      handles.hd_enabled,
      handles.interpolation_type,
      fastoverlayAgent,
    );
    defineOverlayDrawer(
      overlayAgent,
      overlayGridAgent,
      viewboxAgent,
      rendererAgent,
      activeLayerAgent,
      globeAgent,
      fieldAgent,
      sharedStore,
    );
    defineAnimator(
      animatorAgent,
      viewboxAgent,
      globeAgent,
      rendererAgent,
      primaryLayerAgent,
      fieldAgent,
      handles.animation_enabled,
    );

    defineRadiogroup(d3.select("[data-name=interpolation-type]"), handles.interpolation_type);
    defineRadiogroup(d3.select("[data-name=font-size]"), handles.font_size);
    handles.font_size.on($`change`, val => d3.select("html").attr("data-font-size", val ?? null));

    defineKeyboardShortcuts(sharedStore, sessionStore, modalStore, tooltipController, inputController);

    restoreModalStore();
    restoreSessionStore();
    // startHashSync();
    startQuerySync();

    startGoogleAnalyticsNotifier();
    notifyTest();
    notifyApp();

    // defineTaraExtension(rendererAgent);

    console.timeEnd("init");
    done();
  }

  const client = {
    sharedStore,
    sessionStore,
    primaryLayerAgent,
    overlayLayerAgent,
    activeLayerAgent,
    d3,
  };

  // CONSIDER: move reporter and catch to main?
  return new Promise(init)
    .catch(report.error)
    .then(() => client);
}
